package org.osito.jbehave.junit;

import static org.osito.jbehave.junit.StringCleaner.clean;

import org.apache.commons.lang.StringUtils;
import org.jbehave.core.io.StoryPathResolver;
import org.jbehave.core.model.Scenario;
import org.jbehave.core.model.Story;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.TestClass;

public class JUnitStoryRunner extends Runner {

	private Story story;
	private Description desc;
	private final Class<?> klass;

	public JUnitStoryRunner(Class<?> klass) throws InitializationError {
		this.klass = klass;
		story = getStory(createTest(klass));
	}

	private JUnitStory createTest(Class<?> klass) {
		try {
			return (JUnitStory) new TestClass(klass).getOnlyConstructor().newInstance();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public Description getDescription() {
		if (desc == null) {
			desc = Description.createSuiteDescription(getStoryName());
			for (Scenario scenario : story.getScenarios()) {
				desc.addChild(createDescription(scenario));
			}
		}
		return desc;
	}

	private String getStoryName() {
		String name = story.getDescription().asString();
		if (StringUtils.isEmpty(name)) {
			name = "Story";
		}
		return clean(name);
	}

	private Description createDescription(Scenario scenario) {
		return Description.createTestDescription(klass, clean("Scenario: " + scenario.getTitle()));
	}

	@Override
	public void run(RunNotifier notifier) {
		notifier.fireTestRunStarted(getDescription());
		createRunner(notifier).run(new RunNotifier());
	}

	private Runner createRunner(RunNotifier notifier) {
		try {
			return new ActualRunner(klass, new JUnitFormat(notifier, desc));
		} catch (InitializationError e) {
			throw new RuntimeException(e);
		}
	}

	private Story getStory(JUnitStory story) {
		configureAndInitializeStory(story);
		StoryPathResolver pathResolver = story.configuration().storyPathResolver();
		String storyPath = pathResolver.resolve(story.getClass());
		return story.configuredEmbedder().storyRunner().storyOfPath(story.configuration(), storyPath);
	}

	private void configureAndInitializeStory(JUnitStory story) {
		story.setHiddenDryRun(true);
		try {
			new DummyRunner<JUnitStory>(story).run(new RunNotifier());
		} catch (InitializationError e) {
			throw new RuntimeException(e);
		}
	}
}